package cn.mw.cmdb.async;

import cn.mw.cmdb.entity.DataSourceSyncInstanceInfo;
import cn.mw.cmdb.entity.InstanceInfo;
import cn.mw.cmdb.enums.DatasourceScanStatusEnum;
import cn.mw.cmdb.enums.MonitorModeType;
import cn.mw.cmdb.enums.ZabbixAssetsStatusEnum;
import cn.mw.cmdb.mongoMapper.DatasourceSyncInstanceInfoMapper;
import cn.mw.cmdb.param.AssetsScanParam;
import cn.mw.cmdb.param.BatchEditorViewInstance;
import cn.mw.cmdb.param.FunctionModuleEnum;
import cn.mw.cmdb.param.InstanceSearchParam;
import cn.mw.cmdb.service.AssetsManageService;
import cn.mw.cmdb.service.impl.CmdbServiceManage;
import cn.mw.cmdb.service.impl.InstanceServiceImpl;
import cn.mw.cmdb.service.impl.InstanceViewImpl;
import cn.mw.cmdb.util.AssetsTool;
import cn.mw.cmdb.util.CMDBTool;
import cn.mw.components.mongodb.tool.CriteriaWrapper;
import cn.mw.microMonitorCommon.api.common.MwEngineManageService;
import cn.mw.microMonitorCommon.api.scan.ScanResult;
import cn.mw.microMonitorCommon.api.scan.ScanResultSuccess;
import cn.mw.microMonitorCommon.entity.cmdb.DataSourceInstanceVo;
import cn.mw.microMonitorCommon.entity.cmdb.DataSourceVo;
import cn.mw.microMonitorCommon.entity.common.MwEngineManageTableDto;
import cn.mw.microMonitorCommon.entity.zabbix.MWZabbixHostInterfaceDetailVo;
import cn.mw.microMonitorCommon.entity.zabbix.MWZabbixHostInterfaceVo;
import cn.mw.microMonitorCommon.entity.zabbix.MWZabbixHostVo;
import cn.mw.microMonitorCommon.enums.RuleType;
import cn.mw.microMonitorCommon.enums.SecurityProtocolType;
import cn.mw.microMonitorCommon.enums.ServerTypeEnum;
import cn.mw.microMonitorCommon.security.utils.AuthUtils;
import cn.mw.microMonitorCommon.util.SpringUtils;
import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.snmp4j.security.SecurityLevel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class DatasourceAsyncScanService {

    @Autowired
    private AssetsManageService assetsManageService;

    @Autowired
    private CmdbServiceManage cmdbServiceManage;

    @DubboReference(check = false, timeout = 120000, mock = "true")
    private MwEngineManageService mwEngineManageService;

    @Autowired
    private AuthUtils authUtils;

    @Autowired
    private DatasourceSyncInstanceInfoMapper datasourceSyncInstanceInfoMapper;

    public void asyncScan(List<DataSourceInstanceVo> dataSourceInstanceList, DataSourceVo datasourceVo, String taskId) throws Exception {
        List<String> instanceIds = dataSourceInstanceList.stream().map(DataSourceInstanceVo::getInstanceId)
                .filter(id -> !StringUtils.isEmpty(id)).toList();
        SecurityContextHolder.getContext().setAuthentication(authUtils.getJwtAuthenticationToken());
        InstanceServiceImpl instanceService = (InstanceServiceImpl) cmdbServiceManage.getModelService(InstanceServiceImpl.ID);

        String webMonitorModelId = SpringUtils.getPropertiesValue("base.webMonitor.modelId");
        // web监测模型不需要添加纳管
        if (!CollectionUtils.isEmpty(instanceIds) && !StringUtils.equals(webMonitorModelId, datasourceVo.getTargetModelId())) {
            BatchEditorViewInstance params = new BatchEditorViewInstance();
            params.setInstanceIds(instanceIds);
            params.setMw_monitorFlag(true);
            params.setMw_monitorType(ServerTypeEnum.Zabbix.getCode());
            params.setFunctionModule(Arrays.asList(FunctionModuleEnum.enableMonitor.getCode()));
            params.setTimeTaskFlag(true);
            params.setItemAssetsStatus(ZabbixAssetsStatusEnum.NORMAL.getName());
            instanceService.doBatchUpdate(params);
        }

        InstanceSearchParam instanceSearchParam = new InstanceSearchParam();
        instanceSearchParam.setInstanceIds(instanceIds);
        instanceSearchParam.setEnableMonitorStatus(true);
        List<InstanceInfo> instanceInfoList = instanceService.doSelectList(instanceSearchParam);
        Map<String, DataSourceInstanceVo> instanceId2DataSourceVo = dataSourceInstanceList.stream()
                .filter(item -> item.getHostVo() != null)
                .filter(item -> !StringUtils.isEmpty(item.getInstanceId()))
                .collect(Collectors.toMap(DataSourceInstanceVo::getInstanceId, item -> item, (o1, o2) -> o2));
        List<String> proxyIds = instanceId2DataSourceVo.values().stream().map(item -> ((MWZabbixHostVo) item.getHostVo()).getProxy_hostid())
                .filter(proxyId -> !StringUtils.isEmpty(proxyId)).toList();
        List<MwEngineManageTableDto> proxyVoList = mwEngineManageService.getEngineByProxyId(datasourceVo.getEngineId(), proxyIds);
        Map<String, String> proxyId2MwEngineIdMap = proxyVoList.stream().collect(Collectors.toMap(MwEngineManageTableDto::getProxyId, MwEngineManageTableDto::getId, (o1, o2) -> o2));
        DataSourceInstanceVo dataSourceInstanceVo;
        MWZabbixHostVo zabbixHostVo;
        String instanceId;
        List<AssetsScanParam> scanParamsList = new ArrayList<>();
        String proxyId;
        InstanceInfo instanceInfo = null;
        Map<String, InstanceInfo> hostNameAndIp2InstanceInfoMap = new HashMap<>();
        for (int i = 0; i < instanceInfoList.size(); i++) {
            instanceInfo = instanceInfoList.get(i);
            instanceId = instanceInfo.getId();
            if (instanceId2DataSourceVo.containsKey(instanceId)) {
                dataSourceInstanceVo = instanceId2DataSourceVo.get(instanceId);
                zabbixHostVo = (MWZabbixHostVo) dataSourceInstanceVo.getHostVo();
                if (zabbixHostVo == null) {
                    continue;
                }
                proxyId = proxyId2MwEngineIdMap.get(zabbixHostVo.getProxy_hostid());
                MWZabbixHostInterfaceVo mwZabbixHostInterfaceVo = zabbixHostVo.getInterfaces();
                AssetsScanParam scanParam = new AssetsScanParam();
                scanParam.setMw_inBandIp(dataSourceInstanceVo.getHostIp());
                scanParam.setMw_monitorServerId(datasourceVo.getEngineId());
                // 设置引擎id
                scanParam.setMw_pollingEngine(proxyId);
                // 引擎ip不为空的icmp扫描 设置为agent
                if (proxyId != null && mwZabbixHostInterfaceVo != null && MonitorModeType.ICMP.getName().equals(getRuleType(mwZabbixHostInterfaceVo))) {
                    scanParam.setMw_ruleType(RuleType.Agent.getName());
                    scanParam.setMw_port(mwZabbixHostInterfaceVo.getPort());
                } else if (mwZabbixHostInterfaceVo != null) {
                    MWZabbixHostInterfaceDetailVo interfaceDetailVo = mwZabbixHostInterfaceVo.getDetails();
                    scanParam.setMw_ruleType(getRuleType(mwZabbixHostInterfaceVo));
                    scanParam.setMw_port(mwZabbixHostInterfaceVo.getPort());
                    if (interfaceDetailVo != null) {
                        scanParam.setMw_securityLevel(interfaceDetailVo.getSecuritylevel());
                        scanParam.setMw_securityName(interfaceDetailVo.getSecurityname());
                        scanParam.setMw_contextName(interfaceDetailVo.getContextname());
                        scanParam.setMw_authAlgName(interfaceDetailVo.getAuthprotocol());
                        scanParam.setMw_authValue(interfaceDetailVo.getAuthpassphrase());
                        scanParam.setMw_privAlgName(interfaceDetailVo.getPrivprotocol());
                        scanParam.setMw_priValue(interfaceDetailVo.getPrivapassphrase());
                        scanParam.setMw_community(interfaceDetailVo.getCommunity());
                    }
                }
                scanParamsList.add(scanParam);
                if (CollectionUtils.isNotEmpty(zabbixHostVo.getParentTemplates())) {
                    instanceInfo.getData().put(InstanceViewImpl.mw_hosttemplateid, zabbixHostVo.getParentTemplates().get(0).getTemplateid());
                }
                instanceInfo.getData().put(InstanceViewImpl.mw_hostName, dataSourceInstanceVo.getName());
                if (mwZabbixHostInterfaceVo != null) {
                    instanceInfo.getData().put(InstanceViewImpl.mw_ruleType, scanParam.getMw_ruleType());
                    instanceInfo.getData().put(InstanceViewImpl.mw_port, mwZabbixHostInterfaceVo.getPort());
                    if (mwZabbixHostInterfaceVo.getDetails() != null) {
                        MWZabbixHostInterfaceDetailVo interfaceDetailVo = mwZabbixHostInterfaceVo.getDetails();
                        instanceInfo.getData().put(InstanceViewImpl.mw_community, interfaceDetailVo.getCommunity());
                        if (StringUtils.isNotEmpty(interfaceDetailVo.getSecuritylevel())) {
                            instanceInfo.getData().put(InstanceViewImpl.mw_securityLevel, getSecurityLevel(Integer.parseInt(interfaceDetailVo.getSecuritylevel())));
                        }
                        instanceInfo.getData().put(InstanceViewImpl.mw_securityName, interfaceDetailVo.getSecurityname());
                        instanceInfo.getData().put(InstanceViewImpl.mw_contextName, interfaceDetailVo.getContextname());
                        if (StringUtils.isNotEmpty(interfaceDetailVo.getAuthprotocol())) {
                            instanceInfo.getData().put(InstanceViewImpl.mw_authAlgName, getAuthName(Integer.parseInt(interfaceDetailVo.getAuthprotocol())));
                        }
                        instanceInfo.getData().put(InstanceViewImpl.mw_authValue, interfaceDetailVo.getAuthpassphrase());
                        if (StringUtils.isNotEmpty(interfaceDetailVo.getPrivprotocol())) {
                            instanceInfo.getData().put(InstanceViewImpl.mw_privAlgName, getPriAuthName(Integer.parseInt(interfaceDetailVo.getAuthprotocol())));
                        }
                        instanceInfo.getData().put(InstanceViewImpl.mw_priValue, interfaceDetailVo.getPrivapassphrase());
                    }
                    instanceInfo.getData().put(InstanceViewImpl.mw_pollingEngine, proxyId);
                }
                hostNameAndIp2InstanceInfoMap.put(String.format("%s_%s", dataSourceInstanceVo.getName(), dataSourceInstanceVo.getHostIp()), instanceInfo);
            }
        }
        List<ScanResultSuccess> scanResultSuccessList = new ArrayList<>();
        ScanResult scanResult = null;
        if (datasourceVo.isAutoScanFlag()) {
            updateScanResult(instanceInfoList, DatasourceScanStatusEnum.scanning, taskId);
            log.info("begin datasource async Scan start,param:{}", JSON.toJSONString(scanParamsList));
            Map<String, AssetsScanParam> ruleTypeMap = new HashMap<>();
            String ruleKey;
            List<AssetsScanParam> icmpScanParam = new ArrayList<>();
            for (AssetsScanParam assetsScanParam : scanParamsList) {
                AssetsTool.convertSingleScanParam(assetsScanParam);
                if (CollectionUtils.isNotEmpty(assetsScanParam.getRuleParams()) && assetsScanParam.getRuleParams().get(0) != null) {
                    ruleKey = String.format("%s_%s", assetsScanParam.getRuleParams().get(0).genKey(), assetsScanParam.getMw_pollingEngine());
                    if (ruleTypeMap.containsKey(ruleKey)) {
                        ruleTypeMap.get(ruleKey).setIps(ruleTypeMap.get(ruleKey).getIps() + "\n" + assetsScanParam.getMw_inBandIp());
                    } else {
                        ruleTypeMap.put(ruleKey, assetsScanParam);
                    }
                } else {
                    // ICMP扫描
                    icmpScanParam.add(assetsScanParam);
                }
            }
            for (Map.Entry<String, AssetsScanParam> entry : ruleTypeMap.entrySet()) {
                log.info("begin scan,ruleKey:{},ips:{}", entry.getKey(), entry.getValue().getIps());
                try {
                    scanResult = assetsManageService.assetScan(entry.getValue());
                    log.info("end scan,result:{}", JSON.toJSONString(scanResult));
                    if (scanResult != null && CollectionUtils.isNotEmpty(scanResult.getScanResultSuccesses())) {
                        scanResultSuccessList.addAll(scanResult.getScanResultSuccesses());
                    }
                } catch (Exception e) {
                    log.error("assetScan error!", e);
                }
            }
            for (AssetsScanParam assetsScanParam : icmpScanParam) {
                try {
                    scanResult = assetsManageService.assetScan(assetsScanParam);
                    log.info("end icmp scan,result:{}", JSON.toJSONString(scanResult));
                    if (scanResult != null && CollectionUtils.isNotEmpty(scanResult.getScanResultSuccesses())) {
                        scanResultSuccessList.addAll(scanResult.getScanResultSuccesses());
                    }
                } catch (Exception e) {
                    log.error("assetScanIcmp error!", e);
                }
            }
            updateInstanceInfo(scanResultSuccessList, hostNameAndIp2InstanceInfoMap, taskId);
        } else {
            // 更新实例
            instanceService.doBatchUpdateMulti(instanceInfoList);
        }
    }

    private void updateInstanceInfo(List<ScanResultSuccess> scanResultSuccessList, Map<String, InstanceInfo> hostNameAndIp2InstanceInfoMap, String taskId) throws Exception {
        InstanceServiceImpl instanceService = (InstanceServiceImpl) cmdbServiceManage.getModelService(InstanceServiceImpl.ID);
        Map<String, ScanResultSuccess> hostNameAndIP2ScanResultMap = scanResultSuccessList.stream()
                .collect(Collectors.toMap(item -> String.format("%s_%s", item.getHostName(), item.getIpAddress()), item -> item, (o1, o2) -> o2));
        Map<String, ScanResultSuccess> iP2ScanResultMap = scanResultSuccessList.stream()
                .collect(Collectors.toMap(ScanResultSuccess::getIpAddress, item -> item, (o1, o2) -> o2));
        ScanResultSuccess scanResultSuccess = null;
        List<InstanceInfo> scanSuccessInstanceInfoList = new ArrayList<>();
        List<InstanceInfo> scanErrorInstanceInfoList = new ArrayList<>();
        InstanceInfo instanceInfo;
        for (Map.Entry<String, InstanceInfo> item : hostNameAndIp2InstanceInfoMap.entrySet()) {
            instanceInfo = item.getValue();
            if (hostNameAndIP2ScanResultMap.containsKey(item.getKey())) {
                scanResultSuccess = hostNameAndIP2ScanResultMap.get(item.getKey());
            } else if (iP2ScanResultMap.containsKey(instanceInfo.getInBandIp())) {
                scanResultSuccess = iP2ScanResultMap.get(instanceInfo.getInBandIp());
            }
            if (scanResultSuccess == null) {
                log.info("scan result can not find instance info! hostname:{},hostIp:{}", instanceInfo.getHostName(), instanceInfo.getInBandIp());
                scanErrorInstanceInfoList.add(instanceInfo);
                continue;
            }
            instanceInfo.setBrandId(scanResultSuccess.getBrandId());
            instanceInfo.setDeviceCode(scanResultSuccess.getDeviceCode());
            instanceInfo.getData().put(InstanceViewImpl.mw_brand, scanResultSuccess.getBrand());
            instanceInfo.getData().put(InstanceViewImpl.mw_templateName, scanResultSuccess.getMw_templateName());
            instanceInfo.getData().put(InstanceViewImpl.mw_templateId, scanResultSuccess.getTemplateId());
            instanceInfo.getData().put(InstanceViewImpl.mw_specification, scanResultSuccess.getSpecificationId());
            instanceInfo.getData().put(InstanceViewImpl.mw_monitorMode, scanResultSuccess.getMw_monitorMode());
            scanSuccessInstanceInfoList.add(instanceInfo);
        }
        if (CollectionUtils.isNotEmpty(scanSuccessInstanceInfoList)) {
            // 更新实例
            instanceService.doBatchUpdateMulti(scanSuccessInstanceInfoList);
            // 修改实例扫描结果
            updateScanResult(scanSuccessInstanceInfoList, DatasourceScanStatusEnum.scanSuccess, taskId);
        }
        if (CollectionUtils.isNotEmpty(scanErrorInstanceInfoList)) {
            // 更新实例
            instanceService.doBatchUpdateMulti(scanErrorInstanceInfoList);
            // 修改实例扫描结果
            updateScanResult(scanErrorInstanceInfoList, DatasourceScanStatusEnum.scanError, taskId);
        }
    }

    private void updateScanResult(List<InstanceInfo> instanceInfoList, DatasourceScanStatusEnum datasourceScanStatusEnum, String taskId) throws Exception {
        if (CollectionUtils.isNotEmpty(instanceInfoList)) {
            // 修改实例扫描结果
            Query query = new Query(CriteriaWrapper.where(DataSourceSyncInstanceInfo::getInstanceId).in(instanceInfoList.stream().map(InstanceInfo::getId).toList()));
            query.addCriteria(CriteriaWrapper.where(DataSourceSyncInstanceInfo::getTaskId).is(taskId));
            Update update = new Update();
            // 更新数据
            update.set(CMDBTool.getFieldName(DataSourceSyncInstanceInfo::getScanResult), datasourceScanStatusEnum.desc);
            datasourceSyncInstanceInfoMapper.updateMulti(query, update);
        }
    }

    private String getSecurityLevel(int leve) {
        return switch (leve) {
            case 0 -> SecurityLevel.noAuthNoPriv.name();
            case 1 -> SecurityLevel.authNoPriv.name();
            case 2 -> SecurityLevel.authPriv.name();
            default -> null;
        };
    }

    private String getAuthName(int type) {
        return switch (type) {
            case 0 -> SecurityProtocolType.MD5.getName();
            case 1 -> SecurityProtocolType.SHA.getName();
            case 2 -> SecurityProtocolType.SHA224.getName();
            case 3 -> SecurityProtocolType.SHA256.getName();
            case 4 -> SecurityProtocolType.SHA384.getName();
            case 5 -> SecurityProtocolType.SHA512.getName();
            default -> null;
        };
    }

    private String getPriAuthName(int type) {
        return switch (type) {
            case 0 -> SecurityProtocolType.DES.getName();
            case 1 -> SecurityProtocolType.AES128.getName();
            case 2 -> SecurityProtocolType.AES192.getName();
            case 3 -> SecurityProtocolType.SHA256.getName();
            default -> null;
        };
    }

    private String getRuleType(MWZabbixHostInterfaceVo mwZabbixHostInterfaceVo) {
        if (StringUtils.isNotEmpty(mwZabbixHostInterfaceVo.getType())) {
            switch (mwZabbixHostInterfaceVo.getType()) {
                case "1" -> {
                    return MonitorModeType.ICMP.getName();
                }
                case "2" -> {
                    if (mwZabbixHostInterfaceVo.getDetails() != null) {
                        switch (mwZabbixHostInterfaceVo.getDetails().getVersion()) {
                            case "2":
                                return MonitorModeType.SNMPv2.getName();
                            case "3":
                                return MonitorModeType.SNMPv3.getName();
                            default:
                                return MonitorModeType.SNMPv1.getName();
                        }
                    }
                    return MonitorModeType.SNMPv1.getName();
                }
                default -> {
                    return null;
                }
            }
        }
        return null;
    }
}
